package adapter

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	"math"
	"migration-tool/common"
	"os"
	"regexp"
	"sort"
	"strconv"
	"sync/atomic"
	"time"
)

// 只要调度adapter，就需要执行的任务
var NeverFinishedTask map[string]struct{}

// 在增量导出时，需要忽略进度的任务
var AlwaysIncrementTask map[string]struct{}

func init() {
	NeverFinishedTask = make(map[string]struct{})
	NeverFinishedTask[TaskNameCode] = struct{}{}
	NeverFinishedTask[TaskNameWiki] = struct{}{}

	AlwaysIncrementTask = make(map[string]struct{})
	AlwaysIncrementTask[TaskNameMergeRequest] = struct{}{}
	AlwaysIncrementTask[TaskNameIssue] = struct{}{}
}

type TaskState int

// TaskProgress
// 任务状态，增量任务和全量任务同用同一个任务状态
// 任务状态必须在Stop后或是Adapter调度完成后，才会正确保存
// Finish：正常结束时保存为true，错误和未保存都是False
type TaskProgress struct {
	TaskName string `json:"taskName"`
	Finish   bool   `json:"finish"`
}

type TaskProgressArray []TaskProgress

type TaskArgs struct {
	// 导出导入通用
	TaskName       string
	RepoInfo       *RepoInfo
	ExportDataDir  string
	Log            *logrus.Logger
	EnterpriseName string

	// 仅导出使用
	Token           string
	EnterpriseToken string
	PageSize        int
	IncrementFlag   bool
	TimeStamp       time.Time

	// 仅导入使用
	ExportPlatformName string
	Conn               *grpc.ClientConn
	AppKey             string
	AppSecret          string

	// 仅在check时使用，是否将校验失败的数据写入文件
	Write bool
}

type Task struct {
	TaskName  string
	TaskState TaskState
	TaskArgs  TaskArgs
	Func      func(args TaskArgs) error
}

type DefaultAdapter struct {
	cfg            *common.BaseConfig // 配置指针
	runningTaskNum atomic.Int32       // 必须使用atomic.Int32
	conn           *grpc.ClientConn   // 导入时远程rpc服务连接用
	taskMap        map[string]func(args TaskArgs) error
	ctx            context.Context          // 上下文，用于调度时监控是否调用了Stop
	cancel         context.CancelFunc       // 用于Stop调用，终止Task的调度
	repoInfo       *RepoInfo                // 仓库信息
	tasks          map[string]*Task         // 任务，每条OpenApi都封装成一个Task
	log            *logrus.Logger           // 日志
	adapterState   atomic.Int32             // adapter的状态，调度协程、Start、Stop函数可能修改adapter的状态
	taskProgress   map[string]*TaskProgress // 任务完成进度，在adapter被Start()时初始化
	ch             chan taskMsg             // queue，Task结束后向adapter汇报
	errMsg         taskMsg                  // 发生的错误记录下来等待返回
	done           bool                     // 是否任务完成
}

func (adptr *DefaultAdapter) Start() error {
	// 防止重复开始
	if adptr.adapterState.Load() == AdapterRunning {
		return AdapterIsRunningError
	}
	if adptr.adapterState.Load() == AdapterFinish {
		return AdapterIsFinishedError
	}
	adptr.adapterState.Store(AdapterRunning)
	var err error
	adptr.log, err = makeAdapterLog(adptr.cfg, adptr.repoInfo, adptr.cfg.ExportPlatformName)
	if err != nil {
		return err
	}
	err = adptr.makeAllTasks()
	if err != nil {
		return err
	}
	msg := "【" + adptr.repoInfo.RepoGroupName + "-" + adptr.repoInfo.RepoName + "-" + strconv.FormatInt(adptr.repoInfo.RepoId, 10) + "】仓库开始调度"
	adptr.log.Info(msg)

	// 将任务进度情况排序后记录在日志中
	var printProgress TaskProgressArray
	for _, v := range adptr.taskProgress {
		finish := true
		if _, ok := adptr.tasks[v.TaskName]; ok {
			finish = false
		}

		printProgress = append(printProgress, TaskProgress{
			TaskName: v.TaskName,
			Finish:   finish,
		})
	}

	sort.Sort(printProgress)
	var unFinishTask string
	for _, val := range printProgress {
		if !val.Finish {
			if unFinishTask == "" {
				unFinishTask = val.TaskName
				continue
			}
			unFinishTask = unFinishTask + "、" + val.TaskName
		}
	}
	adptr.log.Info(fmt.Sprintf("开始调度 - 本次调度的任务:[%s]", unFinishTask))

	go adptr.schedule()
	return nil
}

func (adptr *DefaultAdapter) Stop() error {
	// 因为可能出现Stop的调用，所以adapter的状态必须atomic
	adptr.adapterState.Store(AdapterStop)
	adptr.cancel()
	return nil
}

func (adptr *DefaultAdapter) GetMessage() (string, error) {
	if adptr.adapterState.Load() == AdapterError {
		msg := "【" + adptr.errMsg.TaskName + "】出现错误:" + adptr.errMsg.Err.Error()
		return "", errors.New(msg)
	}

	if adptr.adapterState.Load() == AdapterStop {
		return LogMsgAdapterSuccessStop, nil
	}
	return LogMsgAdapterSuccess, nil
}

func (adptr *DefaultAdapter) Done() bool {
	return adptr.done
}

func (adptr *DefaultAdapter) schedule() {
	go func() {
		for msg := range adptr.ch {
			recordArgs := RecordArgs{
				Log:      adptr.log,
				TaskName: msg.TaskName,
				Msg:      "",
				Err:      msg.Err,
				Check:    adptr.cfg.Check,
				Export:   adptr.cfg.Export,
			}
			if msg.Err != nil {
				recordArgs.Msg = LogMsgTaskFailed
				if adptr.cfg.Export && msg.Err == NetworkError {
					adptr.tasks[msg.TaskName].TaskState = TaskStateReady
				} else {
					adptr.errMsg = msg
					adptr.tasks[msg.TaskName].TaskState = TaskStateError
				}
			} else {
				recordArgs.Msg = LogMsgTaskSuccess
				adptr.taskProgress[msg.TaskName].Finish = true
				adptr.tasks[msg.TaskName].TaskState = TaskStateFinish
			}
			adptr.runningTaskNum.Add(-1)
			recordTask(recordArgs)
		}
	}()

	for !adptr.Done() {
		stop := false
		select {
		case <-adptr.ctx.Done():
			// 接收到Stop命令，准备退出
			stop = true
			break
		default:
			adptr.scanTaskState()
			time.Sleep(time.Second * time.Duration(IntervalScanTasks))
		}
		if stop {
			break
		}
	}

	// 此时还未执行结束，应当视为正在执行的状态，函数尾部修改状态
	adptr.saveAllTasksProgress()
	if adptr.conn != nil {
		adptr.conn.Close()
	}
	close(adptr.ch)

	// 删除上次增量导出的错误数据
	// 因为已经全量导出了，全量导出为最新数据，所以上次增量导出的数据已经是旧数据了
	// 增量导出优先级又比较高，所以这里删除增量导出的旧数据
	if adptr.cfg.Export && !adptr.cfg.IncrementFlag {
		err := RemoveMergeRequestIncrement(adptr.repoInfo, adptr.cfg)
		if err != nil {
			adptr.log.Error("全量导出结束，删除MergeRequest增量导出文件出现错误")
		}
		err = RemoveMergeRequestMergeFile(adptr.repoInfo, adptr.cfg)
		if err != nil {
			adptr.log.Error("全量导出结束，删除MergeRequest合并文件出现错误")
		}

		err = RemoveIssueIncrement(adptr.repoInfo, adptr.cfg)
		if err != nil {
			adptr.log.Error("全量导出结束，删除Issue增量导出文件出现错误")
		}

		err = RemoveIssueMergeFile(adptr.repoInfo, adptr.cfg)
		if err != nil {
			adptr.log.Error("全量导出结束，删除Issue合并文件出现错误")
		}
	}

	var msg string
	if adptr.done {
		msg = "【" + adptr.repoInfo.RepoGroupName + "-" + adptr.repoInfo.RepoName + "-" + strconv.FormatInt(adptr.repoInfo.RepoId, 10) + "】仓库完成任务，调度正常结束"
	} else {
		msg = "【" + adptr.repoInfo.RepoGroupName + "-" + adptr.repoInfo.RepoName + "-" + strconv.FormatInt(adptr.repoInfo.RepoId, 10) + "】仓库经stop结束"
	}
	adptr.log.Info(msg)
	if adptr.done && adptr.adapterState.Load() != AdapterError {
		// 任务完成后退出，应当记录为finish
		adptr.adapterState.Store(AdapterFinish)
	}
	adptr.done = true
}

func (adptr *DefaultAdapter) scanTaskState() {
	if adptr.adapterState.Load() == AdapterStop {
		return
	}

	minTaskOrder := math.MaxInt
	for key, val := range adptr.cfg.Order {
		if task, ok := adptr.tasks[key]; ok {
			if (task.TaskState == TaskStateReady ||
				task.TaskState == TaskStateRunning) &&
				minTaskOrder > val {
				minTaskOrder = val
			}
		}
	}

	allDone := true
	for k, v := range adptr.tasks {
		if adptr.cfg.Order[k] > minTaskOrder {
			allDone = false
			continue
		}
		switch v.TaskState {
		case TaskStateReady:
			allDone = false
			if (adptr.cfg.Export && int(adptr.runningTaskNum.Load()) < adptr.cfg.MaxParallelFeatureNum) ||
				!adptr.cfg.Export {
				adptr.runningTaskNum.Add(1)
				task := adptr.tasks[k]
				task.TaskState = TaskStateRunning
				recordArgs := RecordArgs{
					Log:      adptr.log,
					TaskName: k,
					Msg:      LogMsgTaskStart,
					Err:      nil,
					Check:    adptr.cfg.Check,
					Export:   adptr.cfg.Export,
				}
				recordTask(recordArgs)
				go func(ch chan taskMsg, task *Task) {
					err := task.Func(task.TaskArgs)
					ch <- taskMsg{
						TaskName: task.TaskName,
						Err:      err,
					}
					defer func() {
						if r := recover(); r != nil {
							// 这里代表ch已关闭，记录一下日志即可
						}
					}()
				}(adptr.ch, task)
			}
		case TaskStateRunning:
			allDone = false
		case TaskStateFinish:
		case TaskStateError:
			v.TaskState = TaskStateAbort
			adptr.adapterState.Store(AdapterError)
		case TaskStateAbort:
		default:
			panic("scanTaskState encounter error taskStates")
		}
	}

	if allDone {
		adptr.done = true
	}
}

// MakeAllTasks 创建所有的任务
// 初始化adapter中和task相关的成员
func (adptr *DefaultAdapter) makeAllTasks() error {
	adptr.ctx, adptr.cancel = context.WithCancel(context.Background())
	adptr.runningTaskNum.Store(0)
	adptr.done = false
	adptr.taskProgress = make(map[string]*TaskProgress)
	adptr.ch = make(chan taskMsg, len(adptr.taskMap))

	// 初始化TaskMap
	taskMapPlatformName := GetPlatformName(adptr.cfg.ExportPlatformName)
	if adptr.cfg.Check {
		// 校验
		adptr.taskMap = CheckFuncMap[taskMapPlatformName]
	} else if adptr.cfg.Export {
		// 导出
		adptr.taskMap = FuncMaps[taskMapPlatformName]
	} else if !adptr.cfg.Export {
		// 导入
		if taskMapPlatformName == GiteePlatformName {
			adptr.taskMap = FuncMaps[AtomGitPlatformName]
		} else if taskMapPlatformName == AonePlatformName {
			adptr.taskMap = FuncMaps[CodeupPlatformName]
		}
	}

	// TODO：导入时，需要初始化连接
	if !adptr.cfg.Export {
		adptr.log.Info("导入时进行连接")
		var err error
		adptr.conn, err = CreateGrpcConnect(adptr.cfg)
		if err != nil {
			adptr.log.Info("出现错误")
			return err
		}
	}

	// 初始化进度状态
	for k := range adptr.taskMap {
		adptr.taskProgress[k] = &TaskProgress{
			TaskName: k,
			Finish:   false,
		}
	}

	// 判断需要加载的任务进度文件名
	var progressFileName string
	if adptr.cfg.Export {
		progressFileName = JsonFileNameExportTaskProgress
	} else {
		progressFileName = JsonFileNameImportTaskProgress
	}

	// 加载之前任务的进度
	dataArgs := DataArgs{
		ExportDataDir:      adptr.cfg.ExportDataDir,
		ExportPlatformName: adptr.cfg.ExportPlatformName,
		FileName:           progressFileName,
		FilePathTmpl:       ExportDataFileTmpl,
		RepoInfo:           adptr.repoInfo,
	}

	filePath, err := MkdirFromStringTmpl(dataArgs)
	if err != nil {
		return err
	}

	//loc, _ := time.LoadLocation("Asia/Shanghai")
	//stat, err := os.Stat(filePath)
	_, err = os.Stat(filePath)
	if err == nil && adptr.cfg.RepoUseProgress {
		//interval := time.Now().Sub(stat.ModTime())
		//// 在时间间隔内，认为进度文件是有效的，否则进度文件就是无效的
		//if interval.Seconds() < DefaultProgressTimeoutSeconds {
		var progress TaskProgressArray
		data, err := os.ReadFile(filePath)
		if err != nil {
			return err
		}
		err = json.Unmarshal(data, &progress)
		if err != nil {
			return err
		}
		// 修改记录的任务状态
		for _, val := range progress {
			if _, ok := adptr.taskProgress[val.TaskName]; ok {
				adptr.taskProgress[val.TaskName].Finish = val.Finish
			}
		}
		//}
	}

	// 开始初始化任务，
	adptr.tasks = make(map[string]*Task)
	for k, v := range adptr.taskMap {
		match := false
		for _, val := range adptr.cfg.Feature {
			if match {
				break
			}
			// val为模版串
			re := regexp.MustCompile(val)
			// 使用模板穿匹配任务名
			m := re.MatchString(k)
			match = m || match
		}

		if !match {
			continue
		}

		// 配置文件中配置了任务，此时就需要忽略该任务的进度
		if _, ok := NeverFinishedTask[k]; ok {
			adptr.taskProgress[k].Finish = false
		}

		// 对于增量的任务，如果增量flag打开，则执行增量导入
		if adptr.cfg.IncrementFlag {
			// 针对AlwaysIncrementTask，如果增量flag打开，则执行增量导出
			// 即AlwaysIncrementTask内的task置为false
			if _, ok := AlwaysIncrementTask[k]; ok {
				adptr.taskProgress[k].Finish = false
			}
		}

		// 任务已完成，则跳过
		if adptr.cfg.RepoUseProgress && adptr.taskProgress[k].Finish {
			continue
		}

		taskArgs := TaskArgs{
			TaskName:           k,
			RepoInfo:           adptr.repoInfo,
			IncrementFlag:      adptr.cfg.IncrementFlag,
			Token:              adptr.cfg.Token,
			EnterpriseToken:    adptr.cfg.EnterpriseToken,
			PageSize:           adptr.cfg.PageSize,
			ExportDataDir:      adptr.cfg.ExportDataDir,
			ExportPlatformName: adptr.cfg.ExportPlatformName,
			TimeStamp:          adptr.cfg.TimeStamp,
			Log:                adptr.log,
			Conn:               adptr.conn,
			AppKey:             adptr.cfg.AppKey,
			AppSecret:          adptr.cfg.AppSecret,
			EnterpriseName:     adptr.cfg.EnterpriseName,
			Write:              adptr.cfg.Write,
		}

		task := &Task{
			TaskName:  k,
			TaskState: TaskStateReady,
			TaskArgs:  taskArgs,
			Func:      v,
		}

		adptr.tasks[k] = task
	}

	return nil
}

func (adptr *DefaultAdapter) saveAllTasksProgress() {
	var progressFileName string
	if adptr.cfg.Check {
		return
	}
	if adptr.cfg.Export {
		progressFileName = JsonFileNameExportTaskProgress
	} else {
		progressFileName = JsonFileNameImportTaskProgress
	}

	// mkdir
	dataArgs := DataArgs{
		ExportDataDir:      adptr.cfg.ExportDataDir,
		ExportPlatformName: adptr.cfg.ExportPlatformName,
		FileName:           progressFileName,
		FilePathTmpl:       ExportDataFileTmpl,
		RepoInfo:           adptr.repoInfo,
	}

	// 获取到FilePath
	filePath, err := MkdirFromStringTmpl(dataArgs)
	if err != nil {
		panic(err)
	}

	var progress TaskProgressArray
	for _, v := range adptr.taskProgress {
		progress = append(progress, *v)
	}

	data, err := json.Marshal(progress)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile(filePath, data, os.ModePerm)
	if err != nil {
		panic(err)
	}

	// 将任务进度情况排序后记录在日志中
	var printProgress TaskProgressArray
	for _, v := range adptr.taskProgress {
		printProgress = append(printProgress, TaskProgress{
			TaskName: v.TaskName,
			Finish:   v.Finish,
		})
	}

	sort.Sort(printProgress)
	var finishTask, unFinishTask string
	for _, val := range printProgress {
		if val.Finish {
			if finishTask == "" {
				finishTask = val.TaskName
				continue
			}
			finishTask = finishTask + "、" + val.TaskName
		} else {
			if unFinishTask == "" {
				unFinishTask = val.TaskName
				continue
			}
			unFinishTask = unFinishTask + "、" + val.TaskName
		}
	}

	if unFinishTask == "" {
		adptr.log.Info(fmt.Sprintf("结束调度- 任务已全部完成，已完成任务:[%s]", finishTask))
	} else {
		adptr.log.Info(fmt.Sprintf("结束调度 - 已完成任务:[%s]、未完成任务:[%s]", finishTask, unFinishTask))
	}

}

type DefaultAdapterArgs struct {
	RepoInfo *RepoInfo
}

func makeDefaultAdapter(cfg *common.Config, args DefaultAdapterArgs) (adptr *DefaultAdapter, err error) {
	adptr = &DefaultAdapter{
		cfg:            &cfg.BaseConfig,
		runningTaskNum: atomic.Int32{},
		conn:           nil,
		taskMap:        nil,
		ctx:            nil,
		cancel:         nil,
		repoInfo:       args.RepoInfo,
		tasks:          nil,
		log:            nil,
		adapterState:   atomic.Int32{},
		taskProgress:   nil,
		ch:             nil,
		errMsg:         taskMsg{},
		done:           false,
	}

	adptr.adapterState.Store(AdapterReady)
	return
}
